/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package co.com.claro.administracionSolicitudes.dao;

import co.com.claro.administracionSolicitudes.dao.exceptions.ClaroException;
import co.com.claro.administracionSolicitudes.dao.exceptions.IllegalOrphanException;
import co.com.claro.administracionSolicitudes.dao.exceptions.NonexistentEntityException;
import co.com.claro.administracionSolicitudes.dao.exceptions.RollbackFailureException;
import co.com.claro.administracionSolicitudes.dto.SolicitudDto;
import co.com.claro.administracionSolicitudes.entity.Aplicacion;
import co.com.claro.administracionSolicitudes.entity.EntidadBase;
import co.com.claro.administracionSolicitudes.entity.EstadosSolicitud;
import java.io.Serializable;
import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import co.com.claro.administracionSolicitudes.entity.Usuario;
import co.com.claro.administracionSolicitudes.entity.Gerencia;
import co.com.claro.administracionSolicitudes.entity.HistoricoSolicitud;
import co.com.claro.administracionSolicitudes.entity.Perfil;
import co.com.claro.administracionSolicitudes.entity.Solicitud;
import co.com.claro.administracionSolicitudes.entity.enums.EstadoActividadEnum;
import co.com.claro.administracionSolicitudes.entity.enums.EstadosSolicitudEnum;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;

/**
 *
 * @author Mike
 */
public class SolicitudJpaController implements Serializable {

    public SolicitudJpaController() {

    }

    @Inject
    private HistoricoSolicitudJpaController historicoSolicitudeJpa;
    @Inject
    private UsuarioJpaController usuarioJpa;
    @Inject
    private GerenciaJpaController gerenciaJpa;
    @PersistenceContext
    private EntityManager em;
    private HistoricoSolicitud historico;

    public void create(Solicitud solicitud) throws RollbackFailureException, Exception {
        try {
            em.persist(solicitud);
            historico = this.crearHistorico(solicitud, solicitud.getEstado());
            historicoSolicitudeJpa.create(historico);
        } catch (Exception ex) {
            throw ex;
        }
    }

    public Solicitud edit(Solicitud solicitud) throws IllegalOrphanException, NonexistentEntityException, RollbackFailureException, Exception {
        String usuario = solicitud.getUsuario();
        try {
            solicitud = em.merge(solicitud);
            solicitud.setUsuario(usuario);
            historico = this.crearHistorico(solicitud, solicitud.getEstado());
            historicoSolicitudeJpa.create(historico);

            return solicitud;
        } catch (Exception ex) {
            throw ex;
        }
    }

    public void destroy(Long id, Solicitud solicitudOriginal) throws IllegalOrphanException, NonexistentEntityException, RollbackFailureException, Exception {

        try {
            Solicitud solicitud;
            try {
                solicitud = em.getReference(Solicitud.class, id);
                solicitud.getIdSolicitud();
            } catch (EntityNotFoundException enfe) {
                throw new NonexistentEntityException("The solicitud with id " + id + " no longer exists.", enfe);
            }
            em.remove(solicitud);
            historico = this.crearHistorico(solicitudOriginal, solicitudOriginal.getEstado());
            historicoSolicitudeJpa.create(historico);
        } catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * Método que realiza inserciones masivas cada 200 registros
     *
     * @param solicitudes
     * @throws RollbackFailureException
     * @throws Exception
     */
    public void insertarSolicitudesMasivas(List<Solicitud> solicitudes) throws RollbackFailureException, Exception {
        int count = 1;

        try {
            for (Solicitud solicitud : solicitudes) {
                em.merge(solicitud);
                if (count == 200) {
                    em.flush();
                    em.clear();
                    count = 1;
                }
                count++;
            }
        } catch (Exception ex) {
            throw ex;
        } finally {
            em.flush();
            em.clear();
        }
    }
    
    public void clearAndFlush(){
        em.flush();
        em.clear();
    }

    public List<Solicitud> findSolicitudesNoPaginacion(SolicitudDto dataTransfer) {
        return findSolicitudes(false, -1, -1, dataTransfer);
    }

    public List<Solicitud> findSolicitudesPaginacion(int firstResult, int maxResult) {
        return findSolicitudes(true, firstResult, maxResult, null);
    }

    public List<Solicitud> findSolicitudesPaginacion(int firstResult, int maxResult, SolicitudDto dataTransfer) {
        return findSolicitudes(true, firstResult, maxResult, dataTransfer);
    }

    private List<Solicitud> findSolicitudes(boolean paginado, int firstResult, int maxResults, SolicitudDto dataTransfer) {

        //SELECT s FROM Solicitud s
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Solicitud> cQuery = builder.createQuery(Solicitud.class);
        Root<Solicitud> sol = cQuery.from(Solicitud.class);
        cQuery.select(sol);

        List<Predicate> predicados = construirPredicados(dataTransfer, sol);

        if (!predicados.isEmpty()) {
            //WHERE [...]
            cQuery.where(predicados.toArray(new Predicate[0]));
        }
        if (dataTransfer.getCampoSorteado() != null) {

            if (dataTransfer.getCampoSorteado().contains(".")) {
                String[] split = dataTransfer.getCampoSorteado().split("\\.");

                Path order = sol.get(split[0]);

                for (int i = 1; i < split.length; i++) {
                    order = order.get(split[i]);
                }

                //ORDER BY [...]
                if (dataTransfer.isAscendiente()) {
                    cQuery.orderBy(builder.asc(order));
                } else {
                    cQuery.orderBy(builder.desc(order));
                }

            } else {
                //ORDER BY [...]
                if (dataTransfer.isAscendiente()) {
                    cQuery.orderBy(builder.asc(sol.get(dataTransfer.getCampoSorteado())));
                } else {
                    cQuery.orderBy(builder.desc(sol.get(dataTransfer.getCampoSorteado())));
                }
            }
        }

        Query query = em.createQuery(cQuery);

        if (paginado) {
            query.setMaxResults(maxResults);
            query.setFirstResult(firstResult);
        }

        List<Solicitud> preResult = query.getResultList();
        List<Solicitud> result = new ArrayList<Solicitud>();

        for (Solicitud solicitud : preResult) {
            EstadosSolicitud estadoSol = solicitud.getEstado();

            if (estadoSol.getNombre() == null) {
                estadoSol.setNombre(EstadosSolicitudEnum.values()[estadoSol.getIdEstado() - 1].getEstado());
            }
            
            solicitud.setEstado(estadoSol);
            result.add(solicitud);
        }
        
        return result;
    }

    private List<Predicate> construirPredicados(SolicitudDto dataTransfer, Root<Solicitud> root) {

        CriteriaBuilder builder = em.getCriteriaBuilder();

        List<Predicate> predicados = new ArrayList<>();

        Predicate idSolicitud, idEstado, idSolicitante, idEjecutor,
                idGerenciaAprobadora, idGerenciaAsignadora, fechaConsulta, idAplicacion,
                idAccion, idPerfil, estado, escalado;

        if (dataTransfer.getIdSolicitud() != null) {
            //s.idSolicitud = :idSolicitud
            idSolicitud = builder.equal(root.get("idSolicitud"), dataTransfer.getIdSolicitud());
            predicados.add(idSolicitud);
        }
        if (dataTransfer.getIdEstado() != null) {
            //s.estado.idEstado = :idEstado
            idEstado = builder.equal(root.get("estado").get("idEstado"),
                    dataTransfer.getIdEstado());
            predicados.add(idEstado);
        }
        if (dataTransfer.getIdSolicitante() != null) {
            //s.idSolicitante.idUsuario = :idSolicitante
            idSolicitante = builder.equal(root.get("idSolicitante").get("idUsuario"),
                    dataTransfer.getIdSolicitante());
            predicados.add(idSolicitante);
        }
        if (dataTransfer.getIdEjecutor() != null) {
            //s.idEjecutor.idUsuario = :idEjecutor
            idEjecutor = builder.equal(root.get("idEjecutor").get("idUsuario"),
                    dataTransfer.getIdEjecutor());
            predicados.add(idEjecutor);
        }
        if (dataTransfer.getIdGerenciaAprobadora() != null) {
            //s.gerenciaAprobadora.idGerencia = :idGerenciaAprobadora
            idGerenciaAprobadora = builder.equal(root.get("gerenciaAprobadora").get("idGerencia"),
                    dataTransfer.getIdGerenciaAprobadora());
            predicados.add(idGerenciaAprobadora);
        }
        if (dataTransfer.getIdGerenciaAsignadora() != null) {
            //s.gerenciaAsignadora.idGerencia = :idGerenciaAsignadora
            idGerenciaAsignadora = builder.equal(root.get("gerenciaAsignadora").get("idGerencia"),
                    dataTransfer.getIdGerenciaAsignadora());
            predicados.add(idGerenciaAsignadora);
        }
        if (dataTransfer.getFechaInicioBusqueda() != null && dataTransfer.getFechaFinBusqueda() != null) {
            //s.fechaSolicitud BETWEEN :fechaInicio AND :fechaFin
            fechaConsulta = builder.between(root.<Date>get("fechaSolicitud"),
                    dataTransfer.getFechaInicioBusqueda(), dataTransfer.getFechaFinBusqueda());
            predicados.add(fechaConsulta);
        }
        if (dataTransfer.getIdAplicacion() != null) {
            //s.aplicacion.idAplicacion = :idAplicacion
            idAplicacion = builder.equal(root.get("aplicacion").get("idAplicacion"),
                    dataTransfer.getIdAplicacion());
            predicados.add(idAplicacion);
        }
        if (dataTransfer.getIdAccion() != null) {
            //s.accion = :idAccion
            idAccion = builder.equal(root.get("accion"), dataTransfer.getIdAccion());
            predicados.add(idAccion);
        }
        if (dataTransfer.getIdPerfil() != null) {
            //s.perfil.idPerfil = :idPerfil
            idPerfil = builder.equal(root.get("perfil").get("idPerfil"),
                    dataTransfer.getIdPerfil());
            predicados.add(idPerfil);
        }
        if (dataTransfer.getNombreSolicitante() != null || dataTransfer.getApellidoSolicitante() != null
                || dataTransfer.getNombreEjecutor() != null || dataTransfer.getApellidoEjecutor() != null) {
            List<Usuario> solicitantes = new ArrayList<Usuario>();
            List<Usuario> ejecutores = new ArrayList<Usuario>();

            if (dataTransfer.getNombreSolicitante() != null && dataTransfer.getApellidoSolicitante() != null) {
                //Buscar solicitantes que tengan los mismos nombres y apellidos
                solicitantes = usuarioJpa.findUsuariosByNombreApellido(dataTransfer.getNombreSolicitante(), dataTransfer.getApellidoSolicitante());
            } else if (dataTransfer.getNombreSolicitante() != null && dataTransfer.getApellidoSolicitante() == null) {
                // Buscar solicitantes con el mismo nombre
                solicitantes = usuarioJpa.findUsuariosByNombre(dataTransfer.getNombreSolicitante());
            } else if (dataTransfer.getNombreSolicitante() == null && dataTransfer.getApellidoSolicitante() != null) {
                // Buscar solicitantes con el mismo apellido
                solicitantes = usuarioJpa.findUsuariosByApellido(dataTransfer.getApellidoSolicitante());
            }

            if (dataTransfer.getNombreEjecutor() != null && dataTransfer.getApellidoEjecutor() != null) {
                //Buscar ejecutores que tengan los mismos nombres y apellidos
                ejecutores = usuarioJpa.findUsuariosByNombreApellido(dataTransfer.getNombreEjecutor(), dataTransfer.getApellidoEjecutor());
            } else if (dataTransfer.getNombreEjecutor() != null && dataTransfer.getApellidoEjecutor() == null) {
                // Buscar ejecutores con el mismo nombre
                ejecutores = usuarioJpa.findUsuariosByNombre(dataTransfer.getNombreEjecutor());
            } else if (dataTransfer.getNombreEjecutor() == null && dataTransfer.getApellidoEjecutor() != null) {
                // Buscar ejecutores con el mismo apellido
                ejecutores = usuarioJpa.findUsuariosByApellido(dataTransfer.getApellidoEjecutor());
            }

            if (solicitantes.size() > 1) {
                List<Predicate> temp = new ArrayList<Predicate>();
                for (Usuario usuario : solicitantes) {
                    // s.idSolicitante = :idSolicitante
                    idSolicitante = builder.equal(root.get("idSolicitante").get("idUsuario"),
                            usuario.getIdUsuario());
                    temp.add(idSolicitante);
                }

                // s.idSolicitante1 = :idSolicitante1 OR [...] OR s.idSolicitanteN = :idSolicitanteN
                predicados.add(builder.or(temp.toArray(new Predicate[temp.size()])));
            } else if (!solicitantes.isEmpty()) {
                // s.idSolicitante = :idSolicitante
                idSolicitante = builder.equal(root.get("idSolicitante").get("idUsuario"),
                        solicitantes.get(0).getIdUsuario());
                predicados.add(idSolicitante);
            }

            if (ejecutores.size() > 1) {
                List<Predicate> temp = new ArrayList<Predicate>();
                for (Usuario usuario : solicitantes) {
                    // s.idEjecutor = :idEjecutor
                    idEjecutor = builder.equal(root.get("idEjecutor").get("idUsuario"),
                            usuario.getIdUsuario());
                    temp.add(idEjecutor);
                }

                // s.idEjecutor1 = :idEjecutor1 OR [...] OR s.idEjecutorN = :idEjecutorN
                predicados.add(builder.or(temp.toArray(new Predicate[temp.size()])));
            } else if (!ejecutores.isEmpty()) {
                // s.idEjecutor = :idEjecutor
                idEjecutor = builder.equal(root.get("idEjecutor").get("idUsuario"),
                        solicitantes.get(0).getIdUsuario());
                predicados.add(idEjecutor);
            }
        }
        if (dataTransfer.getEstados() != null) {
            List<Predicate> tempEstados = new ArrayList<Predicate>();

            for (EstadosSolicitudEnum estadoSolicitud : dataTransfer.getEstados()) {
                // s.estado.idEstado = :idEstado
                estado = builder.equal(root.get("estado").get("idEstado"), estadoSolicitud.getId());
                tempEstados.add(estado);
            }
            // s.estado.idEstado1 = :idEstado1 OR [...] OR s.estado.idEstadoN = :idEstadoN
            predicados.add(builder.or(tempEstados.toArray(new Predicate[tempEstados.size()])));
        }
        if (dataTransfer.isEscalado()) {
            // Buscar la gerencia a la que pertenece al aprobador
            Gerencia gerencia = gerenciaJpa.findGerencia(dataTransfer.getIdGerenciaAprobadora());
            // Buscar el usuario aprobador a partir de la gerencia a la que pertenece
            Usuario aprobador = usuarioJpa.findUsuarioByUsernameExacto(gerencia.getUsernameAprobador(),
                    EstadoActividadEnum.ACTIVO.getId(), EstadoActividadEnum.ACTIVO.getId());

            // s.idSolicitante.idUsuario <> :aprobador
            escalado = builder.notEqual(root.get("idSolicitante").get("idUsuario"), aprobador.getIdUsuario());
            predicados.add(escalado);
        }

        return predicados;
    }

    public Solicitud findSolicitud(Long id) {
        return em.find(Solicitud.class, id);
    }

    public Solicitud findSolicitudByIdSolicitudAndGerenciaAsignadora(Long id, Short idGerencia) {
        Gerencia gerencia = new Gerencia(idGerencia);

        return (Solicitud) em.createNamedQuery("Solicitud.findSolicitudByIdSolicitudAndGerenciaAsignadora")
                .setParameter("idGerencia", gerencia)
                .setParameter("idSolicitud", id).getSingleResult();
    }

    public Solicitud findSolicitudByIdSolicitudAndGerenciaAprobadora(Long id, Short idGerencia) {
        Gerencia gerencia = new Gerencia(idGerencia);

        return (Solicitud) em.createNamedQuery("Solicitud.findSolicitudByIdSolicitudAndGerenciaAprobadora")
                .setParameter("idGerencia", gerencia)
                .setParameter("idSolicitud", id).getSingleResult();
    }

    /**
     * Metodo que busca solicitudes por solicitante, accion y estados de
     * solicitud.
     *
     * @param idSolicitante el ID del usuario solicitante.
     * @param accion la accion por la que se quiere filtrar la busqueda.
     * @param estados los estados por los que se quiere filtrar
     * @return List<Solicitud>, listados con las solicitudes de un solicitante
     * que tengan una accion y un estado perteneciente a un conjunto
     */
    public List<Solicitud> findSolicitudByIdSolicitanteEstadosAccion(Integer idSolicitante, Short accion, List<EstadosSolicitudEnum> estados) {
        Usuario usuario = new Usuario(idSolicitante);

        List<Short> estadosString = new ArrayList<Short>();
        for (EstadosSolicitudEnum estado : estados) {
            estadosString.add(estado.getId());
        }

        List<Solicitud> solicitudes = em.createNamedQuery("Solicitud.findByIdSolicitanteAndEstadosAndAccion")
                .setParameter("idSolicitante", usuario)
                .setParameter("accion", accion)
                .setParameter("estados", estadosString)
                .getResultList();

        return solicitudes;
    }

    /**
     * Método que obtiene una solicitud por el id y por la gerencia a la que
     * pertenece el usuario. Dado que quien realiza esta consulta es aprobador o
     * asignador, se buscará todas las solicitudes de una gerencia especifica.
     * Es decir, que el método buscará el id de la solicitud y que, además,
     * pertenezca a la gerencia del usuario loggeado.
     *
     * @param id, id de la solicitud.
     * @param idGerencia, id de la gerencia a la que pertenece el usuario
     * loggeado.
     * @return Solicitud, la solicitud que coincida con el id y con los usuarios
     * que perteneces a esta gerencia.
     */
    public Solicitud findSolicitudByIdSolicitudAndGerencia(Long id, Short idGerencia) {
        Gerencia gerencia = new Gerencia(idGerencia);

        return (Solicitud) em.createNamedQuery("Solicitud.findSolicitudByIdAndGerencia")
                .setParameter("idGerencia", gerencia)
                .setParameter("idSolicitud", id).getSingleResult();
    }

    /**
     * Método que obtiene las solicitudes filtrando por el id solicitante
     *
     * @param idSolicitante, id del que hace la solicitud
     * @return List<Solicitud>, listado con las solicitudes que haya hecho el
     * solicitante
     */
    public List<Solicitud> findSolicitudBySolicitante(Integer idSolicitante, short idEstado) {
        Usuario solicitante = new Usuario(idSolicitante);
        EstadosSolicitud estado = new EstadosSolicitud(idEstado);
        List<Solicitud> solicitudes = em.createNamedQuery("Solicitud.findSolicitudBySolicitante")
                .setParameter("idSolicitante", solicitante)
                .setParameter("estado", estado).getResultList();

        return solicitudes;
    }

    /**
     * Método que obtiene las solicitudes filtrando por el id de la gerencia
     * asignadora a la que pertenecen las solicitudes.
     *
     * @param idGerencia, id de la gerencia
     * @return List<Solicitud>, listado con las solicitudes que pertenecen a una
     * gerencia.
     */
    public List<Solicitud> findSolicitudByGerenciaAsignadoraAndEstado(Short idGerencia, short idEstado) {
        Gerencia gerencia = new Gerencia(idGerencia);
        EstadosSolicitud estado = new EstadosSolicitud(idEstado);

        List<Solicitud> solicitudes = em.createNamedQuery("Solicitud.findSolicitudByGerenciaAsignadora")
                .setParameter("idGerencia", gerencia).setParameter("estado", estado).getResultList();

        return solicitudes;
    }

    /**
     * Método que obtiene las solicitudes filtrando por el id de la gerencia
     * aprobadora a la que pertenecen las solicitudes.
     *
     * @param idGerencia, id de la gerencia
     * @return List<Solicitud>, listado con las solicitudes que pertenecen a una
     * gerencia.
     */
    public List<Solicitud> findSolicitudByGerenciaAprobadoraAndEstado(Short idGerencia, short idEstado) {
        Gerencia gerencia = new Gerencia(idGerencia);
        EstadosSolicitud estado = new EstadosSolicitud(idEstado);

        List<Solicitud> solicitudes = em.createNamedQuery("Solicitud.findSolicitudByGerenciaAprobadora")
                .setParameter("idGerencia", gerencia).setParameter("estado", estado).getResultList();

        return solicitudes;
    }

    /**
     * Método que obtiene las solicitudes filtrando por el id del ejecutor.
     *
     * @param idEjecutor, id del ejecutor que tiene asignada la solicitud.
     * @return List<Solicitud>, listado con las solicitudes tiene asignadas un
     * ejecutor.
     */
    public List<Solicitud> findSolicitudByEjecutor(Integer idEjecutor, short idEstado) {
        Usuario ejecutor = new Usuario(idEjecutor);
        EstadosSolicitud estado = new EstadosSolicitud(idEstado);
        List<Solicitud> solicitudes = em.createNamedQuery("Solicitud.findSolicitudByEjecutor")
                .setParameter("idEjecutor", ejecutor).setParameter("estado", estado).getResultList();

        return solicitudes;
    }

    /**
     * Método que obtiene las solicitudes filtrando por el id de la gerencia.
     *
     * @param idGerencia, id de la gerencia que tiene asignada la solicitud.
     * @return List<Solicitud>, listado con las solicitudes tiene asignadas una
     * gerencia.
     */
    public List<Solicitud> findSolicitudByGerencia(short idGerencia) {
        Gerencia gerencia = new Gerencia(idGerencia);

        List<Solicitud> solicitudes = em.createNamedQuery("Solicitud.findSolciitudByGerencia")
                .setParameter("idGerencia", gerencia).getResultList();

        return solicitudes;
    }

    public int countByAplicacionAccionPerfilEstados(Integer idAplicacion, short accion, Integer idPerfil, List<EstadosSolicitudEnum> estados) {
        Aplicacion aplicacion = new Aplicacion(idAplicacion);
        Perfil perfil = new Perfil(idPerfil);

        List<Short> estadosString = new ArrayList<Short>();
        for (EstadosSolicitudEnum estado : estados) {
            estadosString.add(estado.getId());
        }

        return ((Long) em.createNamedQuery("Solicitud.countByAplicacionAccionPerfilEstados")
                .setParameter("aplicacion", aplicacion)
                .setParameter("accion", accion)
                .setParameter("perfil", perfil)
                .setParameter("estados", estadosString).getSingleResult()).intValue();
    }

    public int getSolicitudCount() {
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
        Root<Solicitud> rt = cq.from(Solicitud.class);
        cq.select(em.getCriteriaBuilder().count(rt));
        Query q = em.createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

    public int getSolicitudCount(SolicitudDto dataTransfer) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery cQuery = builder.createQuery();
        Root<Solicitud> root = cQuery.from(Solicitud.class);
        cQuery.select(builder.count(root));

        List<Predicate> predicados = construirPredicados(dataTransfer, root);

        if (!predicados.isEmpty()) {
            //WHERE [...]
            cQuery.where(predicados.toArray(new Predicate[0]));
        }

        Query query = em.createQuery(cQuery);

        return ((Long) query.getSingleResult()).intValue();
    }

    public HistoricoSolicitud crearHistorico(EntidadBase entidadBase, EstadosSolicitud estadosSolicitud) throws ClaroException {
        Solicitud solicitud = null;
        if (entidadBase instanceof Solicitud) {
            solicitud = (Solicitud) entidadBase;
        }

        HistoricoSolicitud historicoSol = new HistoricoSolicitud();
        Calendar fechaActual = Calendar.getInstance();

        historicoSol.setUsuarioMovimiento(solicitud.getUsuario());
        historicoSol.setIdSolicitud(solicitud);
        historicoSol.setFechaMovimiento(new Date(fechaActual.getTimeInMillis()));
        historicoSol.setEstadoSolicitud(estadosSolicitud);

        return historicoSol;
    }

}
